<?php
/** 
 * EGP Framework
 *
 * LICENSE
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */

!defined('EGP_LIB_DIR') && die('Access Deny!');

/**
 * Mysql 数据库驱动
 *
 * @package    classes
 * @author     Akon(番茄红了) <aultoale@gmail.com>
 * @copyright  Copyright (c) 2008 (http://www.tblog.com.cn)
 * @license    http://www.gnu.org/licenses/gpl.html     GPL 3
 */
class EGP_Db_Adapter_Mysql extends EGP_Db_Adapter_Abstract
{

    /**
     * 构造方法
     *
     * @param  array  $options  Mysql 连接配置信息
     * @throws EGP_Exception
     */
    public function __construct(array $options)
    {
        if (!isset($options['host']))
            throw new EGP_Exception('Mysql 连接配置缺少主机名 (host)');
        if (!isset($options['dbname']))
            throw new EGP_Exception('Mysql 连接配置缺少数据库名称 (dbname)');
        if (!isset($options['username']))
            throw new EGP_Exception('Mysql 连接配置缺少用户名 (username)');
        if (!isset($options['password']))
            throw new EGP_Exception('Mysql 连接配置缺少密码 (password)');
        $this->_options = $options;
    }

    /**
     * 根据配置信息连接到 MySQL 数据库
     *
     * @throws EGP_Exception
     */
    protected function _connect()
    {
        if ($this->_connection)
            return;
        $this->_connection = @mysql_connect(
            $this->_options['host'],
            $this->_options['username'],
            $this->_options['password'],
            1
        );
        if (!$this->_connection)
           throw new EGP_Exception('无法建立数据库连接：' . mysql_error());
        if (isset($this->_options['charset'])) {
            $charset = $this->_options['charset'];
            if (function_exists('mysql_set_charset')) { //解决mysql字符集安全漏洞
                mysql_set_charset($charset, $this->_connection);
            } else {
                $sql = "SET character_set_connection=$charset, "
                     . "character_set_results=$charset, "
                     . "character_set_client=binary";
                @mysql_query($sql, $this->_connection);
            }
        }
        isset($this->_options['dbname']) && $this->selectDb($this->_options['dbname']);
    }

    /**
     * 关闭数据库连接
     *
     */
    protected function _close()
    {
        mysql_close($this->getConnection());;
    }

    /**
     * 打开指定的数据库
     *
     * @param  string  $dbname  数据库名称
     * @return boolean
     */
    public function selectDb($dbname)
    {
        $link = @mysql_select_db($dbname, $this->getConnection());
        if (!$link)
            throw new EGP_Exception("指定的数据库 $dbname 不存在");
        return $link;
    }

    /**
     * 返回上一个操作中的错误信息的数字编码
     *
     * @return integer
     */
    public function errno()
    {
        return mysql_errno($this->getConnection());
    }

    /**
     * 返回上一个操作产生的文本错误信息
     *
     * @return string
     */
    public function error()
    {
        return mysql_error($this->getConnection());
    }

    /**
     * 执行一条查询语句
     *
     * @param  string  $sql
     * @return resource
     */
    protected function _query($sql)
    {
        return @mysql_query($sql, $this->getConnection());
    }

    /**
     * 插入数据到指定表中
     *
     * @param  string  $table   表名
     * @param  array   $data    需要插入的数据
     * @param  array   $schema  方案(数据库)名
     * @return integer          影响到的记录行数
     */
    public function insert($table, array $data, $schema = null)
    {
        if (is_array(current($data))) {
            foreach ($data as $key => $val) {
                foreach ($val as $k => $v) {
                    $cols[] = $this->quoteAs($k);
                    $vals[] = $this->quote($v);
                }
                if (!isset($columns))
                    $columns = '(' . implode(', ', $cols) . ')';
                $values[] = '(' . implode(', ', array_values($vals)) . ')';
                unset($cols, $vals);
            }
            $values = implode(",\r\n\t", $values);
        } else {
            foreach ($data as $key => $val) {
                $cols[] = $this->quoteAs($key);
                $vals[] = $this->quote($val);
            }
            $columns = '(' . implode(', ', $cols) . ')';
            $values = '(' . implode(', ', array_values($vals)) . ')';
        }

        !empty($schema) && $schema = $this->quoteIdent($schema) . '.';
        $sql = "INSERT INTO $schema" . $this->quoteAs($this->quotePrefix($table)) .
               "\n\t$columns\nVALUES \n\t$values;\n";
        $this->query($sql);

        return $this->rowCount();
    }

    /**
     * 替换数据
     *
     * @param  string  $table   表名
     * @param  array   $data    需要替换的数据
     * @param  array   $schema  方案(数据库)名
     * @return integer          影响到的记录行数
     */
    public function replace($table, array $data, $schema = null)
    {
        if (is_array(current($data))) {
            foreach ($data as $key => $val) {
                foreach ($val as $k => $v) {
                    $cols[] = $this->quoteAs($k);
                    $vals[] = $this->quote($v);
                }
                if (!isset($columns))
                    $columns = '(' . implode(', ', $cols) . ')';
                $values[] = '(' . implode(', ', array_values($vals)) . ')';
                unset($cols, $vals);
            }
            $values = implode(",\r\n\t", $values);
        } else {
            foreach ($data as $key => $val) {
                $cols[] = $this->quoteAs($key);
                $vals[] = $this->quote($val);
            }
            $columns = '(' . implode(', ', $cols) . ')';
            $values = '(' . implode(', ', array_values($vals)) . ')';
        }

        !empty($schema) && $schema = $this->quoteIdent($schema) . '.';
        $sql = "REPLACE INTO $schema" . $this->quoteAs($this->quotePrefix($table)) .
               "\n\t$columns\nVALUES \n\t$values;\n";
        $this->query($sql);

        return $this->rowCount();
    }

    /**
     * 添加 SQL 语句 LIMIT 限制
     *
     * @param  string   $sql
     * @param  integer  $count
     * @param  integer  $offset
     * @return string
     * @thorws Exception
     */
    public function limit($sql, $count, $offset = 0)
    {
        $count = intval($count);
        if ($count <= 0)
            throw new EGP_Exception("无效的 LIMIT 参数 'count=$count'");
        $offset = intval($offset);
        if ($offset < 0)
            throw new EGP_Exception("无效的 LIMIT 参数 'offset=$offset'");
        $sql .= "\nLIMIT $count";
        $offset > 0 && $sql .= " OFFSET $offset";
        return $sql;
    }

    /**
     * 锁定表
     *
     * @return resource
     */
    public function lock($table)
    {
        return $this->query('LOCK TABLE ' . $this->quoteIdent($table) . ' WRITE;');
    }

    /**
     * 解除表锁定
     *
     * @param  string  $table
     * @return resource
     */
    public function unlock($table = null)
    {
        return $this->query('UNLOCK TABLES;');
    }

    /**
     * 开启事务处理
     *
     * @return resource
     */
    public function beginTransaction()
    {
        return $this->query('START TRANSACTION;');
    }

    /**
     * 提交事务
     *
     * @return resource
     */
    public function commit()
    {
        return $this->query('COMMIT;');
    }

    /**
     * 回滚事务
     *
     * @return resource
     */
    public function rollback()
    {
        return $this->query('ROLLBACK;');
    }

    /**
     * 根据查询取出所有资料
     *
     * @param  string  $sql
     * @param  mixed   $mode  MySQL 数据取出模式(默认：MYSQL_ASSOC)
     * @return array
     */
    public function fetchAll($sql, $mode = MYSQL_ASSOC)
    {
        $result = $this->query($sql);
        $data = array();
        while ($row = mysql_fetch_array($result, $mode))
            $data[] = $row;
        return $data;
    }

    /**
     * 根据查询取出所有数据，并作为关联数组及数字数组返回
     *
     * @param  string  $sql
     * @return array
     */
    public function fetchArray($sql)
    {
        return $this->fetchAll($sql, MYSQL_BOTH);
    }

    /**
     * 根据查询取出所有数据，并作为数组对象返回
     *
     * @param  string  $sql
     * @return array
     */
    public function fetchObj($sql)
    {
        $result = $this->query($sql);
        $data = array();
        while ($row = mysql_fetch_object($result))
            $data[] = $row;
        return $data;
    }

    /**
     * 根据查询取出一行数据，并以为数组返回
     *
     * @param  string  $sql
     * @return array
     */
    public function fetchRow($sql)
    {
        $data = $this->fetchAll($sql);
        return isset($data[0]) ? $data[0] : false;
    }

    /**
     * 根据查询取出第一行数据，并返回第一个字段的值
     *
     * @param  string  $sql
     * @return mixed
     */
    public function fetchOne($sql)
    {
        return $this->result($sql, 0);
    }

    /**
     * 取得结果集中指定列的内容
     *
     * @param  string   $sql
     * @param  integer  $row
     * @return mixed
     */
    public function result($sql, $row)
    {
        $result = @mysql_result($this->query($sql), $row);
        if ($this->error())
            throw new EGP_Exception($this->error());
        return $result;
    }

    /**
     * 取得上一次 MySQL 操作所影响的记录行数
     *
     * @return integer
     */
    public function rowCount()
    {
        return mysql_affected_rows($this->getConnection());
    }

    /**
     * 取得结果集中行的数目
     *
     * @param  string  $sql
     * @return integer
     */
    public function numRows($sql)
    {
        return mysql_num_rows($this->query($sql));
    }

    /**
     * 取得上一次 INSERT 操作产生的主键 ID 值
     *
     * @return integer
     */
    public function lastInsertId()
    {
        return mysql_insert_id($this->getConnection());
    }

    /**
     * 过滤转义字符
     *
     * @param  mixed   $value
     * @return string
     */
    protected function _quote($value)
    {
        return mysql_real_escape_string($value, $this->getConnection());
    }

    /**
     * 添加识别引用符
     *
     * @param  string   $value
     * @param  boolean  $auto
     * @return string
     */
    protected function _quoteIdent($value, $auto = false)
    {
        if ($value == '*' || $auto) {
            return $value;
        } else {
            $value = str_replace(' `', ' \`', $value);
            $value = str_replace('` ', '\` ', $value);
            return '`' . $value . '`';
        }
    }

}